package libs

import (
	"net/http"
	"net/url"
	"fmt"
	"crypto/md5"
	"encoding/hex"
	"config"
	"time"
	"math"
	"strconv"
	"log"
	"crypto/hmac"
	"crypto/sha1"
	"strings"
)

// 老版签名算法
func checkPermission(r *http.Request, values url.Values) bool {
	if values.Get("app_id") == "" {
		log.Println("app_id不能为空")
		return false
	}
	if _, ok := config.AppMap[values.Get("app_id")]; !ok {
		log.Println("app_id不存在")
		return false
	}
	// Continue
	appSecret := config.AppMap[values.Get("app_id")]
	if values.Get("sign") == "" {
		log.Println("sign不能为空")
		return false
	}
	if values.Get("request_date") == "" {
		log.Println("request_date不能为空")
		return false
	}
	if timestamp, err := strconv.ParseFloat(values.Get("request_date"), 10); err != nil {
		log.Println("request_date格式错误")
		return false
	} else if math.Abs(float64(time.Now().Unix())-timestamp) > 600 {
		log.Println("请求发起时间与服务器时间差距过大")
		return false
	}
	if checkSign(r, values, appSecret) {
		return true
	} else {
		log.Println("签名校验失败")
		return false
	}
}

func checkSign(req *http.Request, values url.Values, appSecret string) bool {
	sign := values.Get("sign")
	values.Del("sign")
	valEncode, _ := url.QueryUnescape(values.Encode())
	checkStr := fmt.Sprintf("%s?%s%s", req.URL.Path, valEncode, appSecret)
	m5 := md5.New()
	m5.Write([]byte(checkStr))
	return hex.EncodeToString(m5.Sum(nil)) == sign
}

// 接口签名校验
func ValidSignature(r *http.Request) bool {
	signature := r.Form.Get("Signature")
	if signature == "" { //不存在Signature参数时使用老版签名算法
		return checkPermission(r, r.Form)
	}
	timestamp, err := strconv.ParseInt(r.Form.Get("Timestamp"), 10, 64)
	if err != nil {
		log.Println("VailidSignature: " + r.URL.Path + " Timestamp转int64失败")
		return false
	}
	if math.Abs(float64(time.Now().Unix()-timestamp)) > 60 {
		log.Println("VailidSignature: " + r.URL.Path + " Timestamp过期（相差大于60秒）")
		return false
	}
	if appSecret, ok := config.AppMap[r.Form.Get("AppId")]; ok {
		r.Form.Del("Signature")
		strToSign := fmt.Sprintf("%s&%s&%s", r.Method, url.PathEscape(r.URL.Path), r.Form.Encode())
		if signature == encrypt(strToSign, appSecret) {
			exists, err := ExistsKey(signature)
			if err != nil {
				CallDingWebHook("Redis故障告警", "*Redis读取失败*\n- 错误信息："+err.Error())
			} else if exists == false {
				if err := SetKey(signature, 1, 70); err != nil {
					CallDingWebHook("Redis故障告警", "*Redis写入失败*\n- 错误信息："+err.Error())
				}
				return true
			}
			return false
		}
		log.Println("VailidSignature: " + r.URL.Path + " 签名校验失败")
		return false
	}
	log.Println("VailidSignature: " + r.URL.Path + " AppId不存在")
	return false
}

// HMAC-SHA1加密
func encrypt(source, secret string) string {
	source = strings.Replace(source, "~", "%7E", -1) //兼容php sdk，php http_build_query
	h := hmac.New(sha1.New, []byte(secret))
	h.Write([]byte(source))
	return fmt.Sprintf("%x", h.Sum(nil))
}
